package main

import (
	_ "embed"
	"fmt"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"
	"unsafe"

	"github.com/getlantern/systray"
	"github.com/gin-gonic/gin"
	"github.com/go-ole/go-ole"
	"github.com/go-ole/go-ole/oleutil"
	"golang.org/x/sys/windows/registry"
)

// 打包
// go build -ldflags="-H=windowsgui" -o machine_id_server.exe
// 生成图标资源文件
// go install github.com/akavel/rsrc
// rsrc -manifest manifest.xml -ico icon.ico -o rsrc.syso
//
//go:embed icon.ico
var iconData []byte

func createShortcut() error {
	ole.CoInitialize(0)
	defer ole.CoUninitialize()

	shell, err := oleutil.CreateObject("WScript.Shell")
	if err != nil {
		return err
	}
	defer shell.Release()

	ws, err := shell.QueryInterface(ole.IID_IDispatch)
	if err != nil {
		return err
	}
	defer ws.Release()

	exePath, err := os.Executable()
	if err != nil {
		return err
	}

	desktop, err := os.UserHomeDir()
	if err != nil {
		return err
	}
	desktop = filepath.Join(desktop, "Desktop", "机器码服务.lnk")

	shortcut, err := oleutil.CallMethod(ws, "CreateShortcut", desktop)
	if err != nil {
		return err
	}
	dispatch := shortcut.ToIDispatch()
	defer dispatch.Release()

	oleutil.PutProperty(dispatch, "TargetPath", exePath)
	oleutil.PutProperty(dispatch, "WorkingDirectory", filepath.Dir(exePath))
	oleutil.PutProperty(dispatch, "Description", "机器码本地服务")
	_, err = oleutil.CallMethod(dispatch, "Save")
	return err
}

// 创建桌面快捷方式
func checkAndCreateShortcut() {
	desktop, err := os.UserHomeDir()
	if err != nil {
		return
	}
	shortcutPath := filepath.Join(desktop, "Desktop", "机器码服务.lnk")
	if _, err := os.Stat(shortcutPath); os.IsNotExist(err) {
		_ = createShortcut()
	}
}

// 获取主板 UUID
func getMachineID() string {
	cmd := exec.Command("powershell", "-Command", "Get-CimInstance Win32_ComputerSystemProduct | Select-Object -ExpandProperty UUID")
	cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
	output, err := cmd.Output()
	if err != nil {
		return "UNKNOWN"
	}
	return strings.TrimSpace(string(output))
}

// Windows 复制到剪贴板（不依赖第三方库）
func copyToClipboard(text string) error {
	user32 := syscall.NewLazyDLL("user32.dll")
	kernel32 := syscall.NewLazyDLL("kernel32.dll")

	openClipboard := user32.NewProc("OpenClipboard")
	emptyClipboard := user32.NewProc("EmptyClipboard")
	setClipboardData := user32.NewProc("SetClipboardData")
	closeClipboard := user32.NewProc("CloseClipboard")
	globalAlloc := kernel32.NewProc("GlobalAlloc")
	globalLock := kernel32.NewProc("GlobalLock")
	globalUnlock := kernel32.NewProc("GlobalUnlock")
	memcpy := kernel32.NewProc("RtlMoveMemory")

	const CF_UNICODETEXT = 13
	const GMEM_MOVEABLE = 0x0002

	// 打开剪贴板
	openClipboard.Call(0)
	defer closeClipboard.Call()

	emptyClipboard.Call()

	// 分配全局内存
	hMem, _, _ := globalAlloc.Call(GMEM_MOVEABLE, uintptr((len(text)+1)*2))
	ptr, _, _ := globalLock.Call(hMem)

	// 拷贝数据到内存
	utf16Text := syscall.StringToUTF16(text)
	memcpy.Call(ptr, uintptr(unsafe.Pointer(&utf16Text[0])), uintptr(len(utf16Text)*2))

	globalUnlock.Call(hMem)
	setClipboardData.Call(CF_UNICODETEXT, hMem)

	return nil
}

// 添加开机启动
func addToStartup() error {
	exePath, err := os.Executable()
	if err != nil {
		return err
	}
	k, _, err := registry.CreateKey(registry.CURRENT_USER,
		`Software\Microsoft\Windows\CurrentVersion\Run`,
		registry.SET_VALUE)
	if err != nil {
		return err
	}
	defer k.Close()
	return k.SetStringValue("MachineIDServer", exePath)
}

// 删除开机启动
func removeFromStartup() error {
	k, err := registry.OpenKey(registry.CURRENT_USER,
		`Software\Microsoft\Windows\CurrentVersion\Run`,
		registry.SET_VALUE)
	if err != nil {
		return err
	}
	defer k.Close()
	return k.DeleteValue("MachineIDServer")
}

// 检查开机启动是否开启
func isStartupEnabled() bool {
	k, err := registry.OpenKey(registry.CURRENT_USER,
		`Software\Microsoft\Windows\CurrentVersion\Run`,
		registry.QUERY_VALUE)
	if err != nil {
		return false
	}
	defer k.Close()
	_, _, err = k.GetStringValue("MachineIDServer")
	return err == nil
}

func initCors() gin.HandlerFunc {
	return func(c *gin.Context) {
		method := c.Request.Method
		origin := c.Request.Header.Get("Origin")
		if origin != "" {
			c.Header("Access-Control-Allow-Origin", "*")
			c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
			c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization")
			c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type")
			c.Header("Access-Control-Allow-Credentials", "true")
		}
		if method == "OPTIONS" {
			c.AbortWithStatus(http.StatusNoContent)
		}
		c.Next()
	}
}

func startHTTPServer() {
	router := gin.Default()
	router.Use(initCors())
	router.GET("/getMachineId", func(c *gin.Context) {
		id := getMachineID()
		c.JSON(200, gin.H{"machineId": id})
	})

	err := router.Run("127.0.0.1:10001")
	if err != nil {
		fmt.Println("HTTP 服务启动失败:", err)
		os.Exit(1)
	}
}

func onReady() {
	systray.SetIcon(iconData)
	systray.SetTitle("机器码服务")
	systray.SetTooltip("机器码本地服务正在运行")

	mCopy := systray.AddMenuItem("复制机器码", "将机器码复制到剪贴板")
	mStartup := systray.AddMenuItem("开机自启动", "切换开机自启动")
	systray.AddSeparator()
	mQuit := systray.AddMenuItem("退出", "退出程序")
	if !isStartupEnabled() {
		_ = addToStartup()
	}
	if isStartupEnabled() {
		mStartup.Check()
	} else {
		mStartup.Uncheck()
	}

	go startHTTPServer()

	go func() {
		for {
			select {
			case <-mCopy.ClickedCh:
				id := getMachineID()
				_ = copyToClipboard(id)
				fmt.Println("机器码已复制:", id)
			case <-mStartup.ClickedCh:
				if isStartupEnabled() {
					_ = removeFromStartup()
					mStartup.Uncheck()
				} else {
					_ = addToStartup()
					mStartup.Check()
				}
			case <-mQuit.ClickedCh:
				systray.Quit()
				return
			}
		}
	}()
}

func onExit() {}

func main() {
	checkAndCreateShortcut()
	systray.Run(onReady, onExit)
}
